package task07;

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * ProducerConsumer
 * <p/>
 * Producer and consumer tasks in a desktop search application
 *
 * @author Brian Goetz and Tim Peierls
 */
public class ProducerConsumer {
    class FileCrawler implements Runnable {
        private final File root;
        private int currentDeque;

        public FileCrawler(File root) {
            this.root = root;
        }

        @Override
        public void run() {
            try {
                crawl(root);
                crawlerLatch.countDown();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }

        private void crawl(File root) throws InterruptedException {
            File[] entries = root.listFiles(fileFilter);
            if (entries != null) {
                for (File entry : entries)
                    if (entry.isDirectory())
                        crawl(entry);
                    else if (!alreadyIndexed(entry))
                        fileDequeList.get(nextDeque()).put(entry);
            }
        }

        private boolean alreadyIndexed(File f) {
            return false;
        }

        private int nextDeque() {
            return currentDeque++ % fileDequeList.size();
        }
    }

    class Indexer implements Runnable {
        private final BlockingDeque<File> fileDeque;

        public Indexer(BlockingDeque<File> fileDeque) {
            this.fileDeque = fileDeque;
        }

        @Override
        public void run() {
            try {
                while (true) {
                    if (!fileDeque.isEmpty()) {
                        synchronized (fileDeque) {
                            if (!fileDeque.isEmpty())
                                indexFile(fileDeque.takeFirst());
                        }
                    }
                    else {
                        for (BlockingDeque<File> deque : fileDequeList) {
                            if (!deque.isEmpty()) {
                                synchronized (deque) {
                                    if (!deque.isEmpty()) {
                                        indexFile(deque.takeLast());
                                        break;
                                    }
                                }
                            }
                        }
                        if (crawlerLatch.getCount() > 0)
                            Thread.yield();
                        else
                            return;
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }

        private void indexFile(File file) {
            System.out.println(Thread.currentThread().getName() + " " + file.getPath() + file.getName());
        }
    }

    private final List<BlockingDeque<File>> fileDequeList;
    private final CountDownLatch crawlerLatch;

    private final FileFilter fileFilter;
    private final File[] roots;

    private static final int BOUND = 10;
    private static final int N_CONSUMERS = 10; // Runtime.getRuntime().availableProcessors();

    public ProducerConsumer(File[] roots) {
        fileDequeList = new ArrayList<>();
        for (int i = 0; i < N_CONSUMERS; i++)
            fileDequeList.add(new LinkedBlockingDeque<File>(BOUND));

        crawlerLatch = new CountDownLatch(roots.length);

        fileFilter = new FileFilter() {
            @Override
            public boolean accept(File file) {
                return true;
            }
        };

        this.roots = roots;
    }

    public void startIndexing() {
        for (File root : roots)
            new Thread(new FileCrawler(root)).start();

        for (int i = 0; i < N_CONSUMERS; i++)
            new Thread(new Indexer(fileDequeList.get(i))).start();
    }
}